package org.ncds.utils;

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RetriableTask<T> implements Callable<T> {
	Logger logger = LoggerFactory.getLogger(RetriableTask.class);
	/**
	 * @ A simple RetriableTask extendable class taken from the internet and customized
	 * includes linear decay option
	 * defaults to 5 RETRIES and 1 SEC WAIT between attemtps
	 */
	private Callable<T> task;
	 public static final int DEFAULT_NUMBER_OF_RETRIES = 5;
	 public static final long DEFAULT_WAIT_TIME = 1000;
	 public static final boolean DECAY = false;
	 public static final boolean LOG = false;
	 
	 private int numberOfRetries; // total number of tries
	 int numberOfTriesLeft; // number left
	 private long timeToWait; // wait interval
	 private boolean decay;
	 private double decayRate;
	 private boolean doLog;
	 
	 public RetriableTask(Callable<T> task) {
	  this(DEFAULT_NUMBER_OF_RETRIES, DEFAULT_WAIT_TIME, task);
	 }
	 
	 
	 
	 public RetriableTask(int numberOfRetries, 
			 				long timeToWait,
			 				Callable<T> task) {
		 
		 this.numberOfRetries = numberOfRetries;
		 numberOfTriesLeft = numberOfRetries;
		 this.timeToWait = timeToWait;
		 this.decay = false;
		 this.decayRate=timeToWait/numberOfRetries;
		 this.doLog = false;
		 this.task = task;
		 
	 }
	 
	 
	 public RetriableTask(int numberOfRetries, 
			 					long timeToWait, 
			 					boolean decay,
			 					Callable<T> task) {
		 
		 this.numberOfRetries = numberOfRetries;
		 numberOfTriesLeft = numberOfRetries;
		 this.timeToWait = timeToWait;
		 this.decay = decay;
		 this.decayRate=timeToWait/numberOfRetries;
		 this.doLog = false;
		 this.task = task;
	 }
	 
	 
	 
	 public RetriableTask(int numberOfRetries, 
			 				long timeToWait, 
			 				boolean decay,
			 				double decayRate, 
			 				Callable<T> task) {
		 
		 
		 this.numberOfRetries = numberOfRetries;
		 numberOfTriesLeft = numberOfRetries;
		 this.timeToWait = timeToWait;
		 this.decay = decay;
		 this.decayRate=decayRate;
		 this.doLog = false;
		 this.task = task;
		 
	 }
	 
	 
	 
	 
	 public RetriableTask(int numberOfRetries, 
			 					long timeToWait, 
			 					boolean decay,
			 					boolean doLog, 
			 					double decayRate, 
			 					Callable<T> task) {
		 
		 this.numberOfRetries = numberOfRetries;
		 numberOfTriesLeft = numberOfRetries;
		 this.timeToWait = timeToWait;
		 this.decay = decay;
		 this.decayRate=decayRate;
		 this.doLog = doLog;
		 this.task = task;
		 
	 }
	 
	 
	 
	 
	 public T call() throws Exception {
		 
		 long new_timeToWait=this.timeToWait;
		  while (true) {
			   try {
			    return task.call();
			   } catch (InterruptedException e) {
			    throw e;
			   } catch (CancellationException e) {
			    throw e;
			   } catch (Exception e) {
				   numberOfTriesLeft--;
				   if (numberOfTriesLeft == 0) {
					   throw new RetryException(numberOfRetries +
							   				" attempts to retry failed at " + timeToWait +
							   				"ms interval decay=" + String.valueOf(this.decay), e);
				   }
				   
				   if (!decay) {
					   Thread.sleep(timeToWait);
				   } else {
					   //TODO calc new timetoWait based on decay capability
					   new_timeToWait = (long)(1+((numberOfRetries-numberOfTriesLeft)*decayRate))*timeToWait;
					   Thread.sleep(new_timeToWait);
				   }
				   
				   if (doLog) {
					   logger.info("retry try " + String.valueOf(numberOfRetries-numberOfTriesLeft+1) 
							   							+ " of " + String.valueOf(numberOfRetries)
							   							+ " sleepTime " + new_timeToWait);
				   }
			   }
		  }
	 }

	 public int getNumberOfRetries() {
		 return this.numberOfRetries;
	 }
	 
	 
	 public void setNumberOfRetries(int n) {
		 this.numberOfRetries = n;
	 }
	 
	 public int getnumberOfTriesLeft() {
		 return this.numberOfTriesLeft;
	 }
	 
	 public void setnumberOfTriesLeft(int n) {
		 this.numberOfTriesLeft = n;
	 }
	 
	 public long gettimeToWait() {
		 return this.timeToWait;
	 }
	 
	 public void settimeToWait(long n) {
		 this.timeToWait = n;
	 }
	 
	 public boolean getdecay() {
		 return this.decay;
	 }
	 
	 
	 public void setdecay(boolean n) {
		 this.decay = n;
	 }
	 
	 public double getdecayRate() {
		 return this.decayRate;
	 }
	 
	 
	 public void setdecayRate(double n) {
		 this.decayRate = n;
	 }
	 
	 public boolean getdoLog() {
		 return this.doLog;
	 }
	 
	 public void setdoLog(boolean n) {
		 this.doLog = n;
	 }
	 
	 
}
